#ifndef FASYS_FASYS_SUBSCRIPTION_CONTAINER_HPP
#define FASYS_FASYS_SUBSCRIPTION_CONTAINER_HPP

#include "types.hpp"
#include "tags.hpp"
#include "subscription_info.hpp"

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>

namespace fasys{

using namespace boost;
using namespace boost::multi_index;

struct subscription_ordered_by_subscribe_id_t
  : ordered_unique<
      tag<_subscribe_id_>,
      composite_key<
        subscription_info,
        member<subscription_info, subscribe_id_t, &subscription_info::subscribe_id>
      >
   >
{
};

struct subscription_ordered_by_uuid
  : ordered_unique<
      tag<_uuid_>,
      composite_key<
        subscription_info,
        member<subscription_info, std::string, &subscription_info::uuid>
      >
   >
{
};

struct subscription_ordered_by_death_time
  : ordered_non_unique<
      tag<_death_time_>,
      composite_key<
        subscription_info,
        member<subscription_info, time_t, &subscription_info::death_time>
      >
   >
{
};

struct subscription_ordered_by_observer
  : ordered_non_unique<
      tag<_observer_>,
      composite_key<
        subscription_info,
        member<subscription_info, isubscriber*, &subscription_info::observer>
      >
   >
{
};

struct subscription_indices
  : indexed_by<
      subscription_ordered_by_subscribe_id_t,
      subscription_ordered_by_uuid,
      subscription_ordered_by_death_time,
      subscription_ordered_by_observer
    >
{};

typedef multi_index_container<subscription_info, subscription_indices> subscription_multi_index;

struct f_update_observer
{
  isubscriber* observer;
  time_t now;
  f_update_observer(isubscriber* observer)
    : observer(observer)
    , now(time(0))
  {
  }
  void operator()(subscription_info& s)
  {
    s.observer = observer;
    s.subject->observer = observer;
    s.death_time = now + s.lifetime;
  }
};

class subscription_container
  : private subscription_multi_index
{
  typedef subscription_multi_index super;
  typedef super::index<_subscribe_id_>::type by_subscribe_id_type;
  typedef super::index<_uuid_>::type by_uuid_type;
  typedef super::index<_death_time_>::type by_death_time_type;
  typedef super::index<_observer_>::type by_observer_type;

public:

  void remove_death(time_t now )
  {
    by_death_time_type& by_death_time = super::get<_death_time_>();
    by_death_time_type::const_iterator itr = by_death_time.lower_bound( now );
    // TODO: уведомить подписчиков (в деструкторе subscription делать не лучшая идея)
    by_death_time.erase( by_death_time.begin(), itr );
  }

  void subscribe(const std::string& key, const std::string& uuid, isubscriber* observer)
  {
    by_uuid_type& by_uuid = super::get<_uuid_>();
    by_uuid_type::iterator itr = by_uuid.find(uuid);
    if (itr != by_uuid.end() )
    {
      if (itr->key == key)
        by_uuid.modify(itr, f_update_observer(observer) );
      else
        throw;
    }
  }

  void describe(const std::string& key, const std::string& uuid, isubscriber* observer)
  {
    by_uuid_type& by_uuid = super::get<_uuid_>();
    by_uuid_type::iterator itr = by_uuid.find(uuid);
    if (itr != by_uuid.end() )
    {
      if (itr->key == key)
      {
        if ( itr->observer == observer )
          by_uuid.modify(itr, f_update_observer(0) );
        else
          throw;
      }
      else
        throw;
    }
  }

  void move_messages(const std::string& key, const std::string& uuid, std::list<message>& ml )
  {
    by_uuid_type& by_uuid = super::get<_uuid_>();
    by_uuid_type::iterator itr = by_uuid.find(uuid);
    if (itr != by_uuid.end() )
    {
      if (itr->key == key)
      {
        itr->subject->move_messages(ml);
      }
      else
        throw;
    }
  }

  void insert(subscription_info& info)
  {
    time_t now = time(0);
    info.death_time = now + info.lifetime;
    super::insert( info );
  }

  subscription* find(const std::string& key, const std::string& uuid)
  {
    by_uuid_type& by_uuid = super::get<_uuid_>();
    by_uuid_type::iterator itr = by_uuid.find(uuid);
    if (itr != by_uuid.end() )
    {
      if (itr->key!=key) return 0;
      return itr->subject;
    }
    return 0;
  }
};





}

#endif
